from argparse import ArgumentParser
from io import StringIO
from os.path import join
from sys import stdout
from tokenize import generate_tokens

from .exceptions import GeneratorException
from .generator import Generator
from .root import MEMODEATH_ROOT
from .tokenizer import Tokenizer

DEFAULT_META_PATH = join(MEMODEATH_ROOT, 'meta.gram')

_GRAMMAR_FILE_FAIL = object()


def _generate(
        gram_file, class_name, gramparser_class=...,
        show_gram=False, show_lr=False, raise_syntax_error=True):
    if gramparser_class is not ...:
        Dodoco = gramparser_class
    else:
        from .gramparser import Dodoco

    with open(gram_file, encoding='UTF-8') as f:
        parser = Dodoco(
            Tokenizer(generate_tokens(f.readline)),
            gram_file
        )
        gram = parser.rule_grammar()

        if gram is None:
            if raise_syntax_error:
                parser.raise_syntax_error_from_last_token()
            return _GRAMMAR_FILE_FAIL

        if show_gram:
            return gram

        string = StringIO()
        gen = Generator()

        if show_lr:
            return gen.prepare_left_recursive_names(gram)

        gen.generate(class_name, gram, string)

    return string.getvalue()


def memodeath_main():
    arg_parser = ArgumentParser(
        description='a python PEG parser generator',
        prog='memodeath')
    arg_parser.add_argument(
        'grammar', help='the PEG grammar file name')
    arg_parser.add_argument(
        '-o',
        help='the parser output file name (write to stdout if it\'s not specified)')
    arg_parser.add_argument(
        '-c', help='parser class name (default = MyParser)',
        default='MyParser')
    arg_parser.add_argument(
        '--meta', help='generate the grammar parser again with the provided META file'
                       ' (\'~\' means using %s)' % DEFAULT_META_PATH)
    arg_parser.add_argument(
        '--exec', action='store_true',
        help='execute the generated parser source after successfully generated')
    arg_parser.add_argument(
        '--boot',
        help='ignore \'--exec\' and boot the parser (require meta \'boot\')')
    arg_parser.add_argument(
        '--gram', action='store_true',
        help='display the grammar structure')
    arg_parser.add_argument(
        '--token', action='store_true',
        help='show tokens')
    arg_parser.add_argument(
        '--lr', action='store_true',
        help='show left recursive rules in grammar file.')
    arg_parser.add_argument(
        'extras', nargs='*', help='extra arguments, unnecessary.')

    opts = arg_parser.parse_args()

    gram_file = opts.grammar

    dodoco = ...

    if opts.meta is not None:
        if opts.meta == '~':
            opts.meta = DEFAULT_META_PATH
        _source = _generate(opts.meta, 'Dodoco')
        ns = {}
        exec(compile(_source, filename='<dodoco>', mode='exec'),
             ns)
        dodoco = ns['Dodoco']

    if opts.token:
        print('\n'.join(str(x) for x in generate_tokens(open(opts.grammar).readline)))
        return 0

    try:
        source = _generate(gram_file, opts.c, dodoco, opts.gram, opts.lr)
    except GeneratorException as e:
        print('memodeath: generator error: %s' % e.args)
        return 1
    except SyntaxError as e:
        import traceback
        traceback.print_exception(e.__class__, e, None)
        return 1

    if source is _GRAMMAR_FILE_FAIL:
        print('memodeath: Failed to parse grammar file')
        return 1

    if opts.gram:
        from .formatter import format_object
        print(format_object(source))
        return 0

    if opts.lr:
        print(source)
        return 0

    if opts.boot is not None:
        from .tokenizer import Tokenizer

        ns = {}
        exec(compile(source, filename='<boot>', mode='exec'), ns)
        if (bootinfo := ns.get('__BOOTINFO__')) is None:
            print('memodeath: the grammar file is not bootable')
            return 1

        cls, func_name = bootinfo
        obj = cls(
            Tokenizer(generate_tokens(open(opts.boot, encoding='UTF-8').readline)),
            opts.boot
        )
        if (func := getattr(obj, f'rule_{func_name}', None)) is None:
            print('memodeath: boot entrance \'%s\' not found' % func_name)
            return 1

        print(func())

        return 0
    elif opts.exec:
        import sys
        sys.argv = [sys.argv[0]] + opts.extras
        exec(source,
             {'__name__': '__main__'})
        return 0

    file = stdout
    if opts.o is not None:
        file = open(opts.o, 'w', encoding='UTF-8')

    print(source, file=file)

    return 0
